package miniworld;

import homeMadeExceptions.NetworkException;
import homeMadeExceptions.Routing_HostNotAvailableException;
import homeMadeExceptions.Routing_HostNotReachableException;
import homeMadeExceptions.ScatterNetException;
import homeMadeExceptions.ScatterNet_HostCannotJoinException;
import homeMadeExceptions.ScatterNet_HostIsAlreadyMasterException;

import java.util.HashMap;
import java.util.Vector;

import javax.swing.JOptionPane;

import miniworld.LocalSimulationQueue._call_method;

import simulationEngine.SimulationEngine;
import simulationEnviroment.Clock;
import datatypes.BluetoothDeviceAddress;
import datatypes.Host;
import datatypes.HostAction;
import datatypes.HostActionType;
import datatypes.LogicalNetworkActionType;
import datatypes.Message;
import datatypes.NetworkAction;
import datatypes.PhysicalNetworkActionType;
import datatypes.RosterAction;

public class Miniworld {
//Variables
	private SimulationEngine engine = null;
	
	private PhysicalNetwork physicalNetwork;
	private LogicalNetwork logicalNetwork; 
	private Roster roster;
	private ScatterNet scatternet;
		
	private int miniworldWidth = -1;
	private int miniworldHeight = -1;
	private int distance = -1;
	private int simulationID = -1;
	
	private int notificationCount = Integer.MAX_VALUE ;
	
	
	
	public Miniworld (SimulationEngine engine){
		this.engine = engine;
		this.simulationID = engine.getID();
		this.miniworldWidth = engine.getScenarioParameter().getRosterWidth();
		this.miniworldHeight = engine.getScenarioParameter().getRosterHeight();
		this.distance = engine.getScenarioParameter().getRange();
		//for initialisation
		this.getLogicalNetwork();
		this.getScatternet();
		this.getPhysicalNetwork();
	}
	
	private LogicalNetwork getLogicalNetwork(){
		if (logicalNetwork == null){
			logicalNetwork = new LogicalNetwork(engine);
		}
		return logicalNetwork;
	}

	protected PhysicalNetwork getPhysicalNetwork(){
		if (physicalNetwork == null){
			physicalNetwork = new PhysicalNetwork(this.getEngine().getID());
		}
		return physicalNetwork;
	}
	
	protected ScatterNet getScatternet(){
		if (scatternet == null){
			scatternet = new ScatterNet(this);
		}
		return scatternet;
	}

	protected Roster getRoster(){
		if (roster == null){
			roster = new Roster(this.simulationID, engine.getScenarioParameter().getID());
			roster.createRoster(miniworldWidth, miniworldHeight, distance);
		}
		return roster;
	}

	
	private SimulationEngine getEngine(){
		return this.engine;
	}
	
	
	//API for Simulation Enviroment

	/**
	 * used by Processor
	 */
	public void initMiniworld (){
		Vector<HostAction> enableDeviceActions = new Vector<HostAction>();
		//Place active Hosts in Roster
		for ( Host elem : engine.getHostList().getActiveHosts().values() ){
			this.getRoster().placeNewHost(elem.getID());
			enableDeviceActions.add(new HostAction(elem.getBd_Addr(), HostActionType.ENABLE_DEVICE_DP_API));
		}
		//Place passive Hosts in Roster
		for ( Host elem : engine.getHostList().getPassiveAvailableHosts().values() ){
			this.getRoster().placeNewHost(elem.getID());			
		}
		//Call Discovery-Protocol and enable every Device in Network at Startup
		engine.getSimulationEnviroment().getClock().onCleanroomCall(enableDeviceActions);
	}

	/**
	 * used in Clock
	 */
	public void doManipulate(){
		Vector<RosterAction> tmp = this.engine.getSimulationEnviroment().getProcessor().getSabotageStack();
		for (int i=0 ; i < tmp.size(); i++){
			this.getRoster().executeAction(tmp.get(i));
		}
		Vector<HostAction> tmpHostA = this.engine.getSimulationEnviroment().getProcessor().getHostActions();
		if (notificationCount >= Clock.getNotificationRate()){
			notificationCount = 0;
		} else {
			notificationCount ++;			
		}
		for ( HostAction action : tmpHostA ){
			switch (action.getType()){
			case ENABLE_DEVICE_DP_API:
				if (engine.getHostList().getHost(action.getDevice())==null){
					System.out.println(engine.getHostList().getPassiveAvailableHosts().values().size());	
				}
				try {
					if (getEngine().getSimulationEnviroment().getClock().getCurrentTime() < 0){
						(engine.getHostList().getHost(action.getDevice())).getDiscoverySocket().enableDiscoveryMechanism();		
					} else {
						(engine.getHostList().getHost(action.getDevice())).getLocalQueue().add_method_call(_call_method.DISCOVERY_SERVICE_ENABLE, null);
					}
					} catch (NullPointerException e) {
						// TODO Auto-generated catch block
						//e.printStackTrace();
						JOptionPane.showMessageDialog(null, "Device not existing in Simulation" + this.getClass(),
								"Fehler in Simulationskonfiguration",
								JOptionPane.INFORMATION_MESSAGE);
					}				
				break;
			case DISABLE_DEVICE_DP_API:
				(engine.getHostList().getHost(action.getDevice())).getLocalQueue().add_method_call(_call_method.DISCOVERY_SERVICE_DISABLE, null);
				//(engine.getHostList().getHost(action.getDevice())).getDiscoverySocket().disableDiscoveryMechanism();				
				break;
			case TIMESTEP_NOTIFIER:
				if (notificationCount >= Clock.getNotificationRate()){
					(engine.getHostList().getHost(action.getDevice())).getLocalQueue().add_method_call(_call_method.DISCOVERY_NOTIFICATION, null);
					//.getDiscoverySocket().notificationAPI();
				} else {
					(engine.getHostList().getHost(action.getDevice())).getLocalQueue().add_method_call(_call_method.ROUTING_NOTIFICATION, null);
					//(engine.getHostList().getHost(action.getDevice())).getRoutingSocket().notificationAPI();	
				}
				break;
			case DELETE_SERVICE:
				if (true == true){
					Vector<Object> parameterlist = new Vector<Object>();
					parameterlist.add(action.getServiceName().firstElement());
					(engine.getHostList().getHost(action.getDevice())).getLocalQueue().add_method_call(_call_method.DISCOVERY_DELETE_LOCAL_SERVICE, parameterlist);	
				}
				break;
			case PROPAGATE_NEW_SERVICE:
				if (true == true){
					Vector<Object> parameterlist = new Vector<Object>();
					parameterlist.add(action.getServiceName().firstElement());
					(engine.getHostList().getHost(action.getDevice())).getLocalQueue().add_method_call(_call_method.DISCOVERY_PUB_LOCAL_SERVICE, parameterlist);	
				}
				break;
			case REQUEST_SERVICE:
				if (true == true){
					Vector<Object> parameterlist = new Vector<Object>();
					parameterlist.add(action.getServiceName().firstElement());
					(engine.getHostList().getHost(action.getDevice())).getLocalQueue().add_method_call(_call_method.DISCOVERY_FIND_SPECIFIC_SERVICE, parameterlist);	
				}
				break;
			case UPDATE_SERVICE:
				if (true == true){
					Vector<Object> parameterlist = new Vector<Object>();
					parameterlist.add(action.getServiceName().firstElement());
					parameterlist.add(action.getServiceName().get(1));
					(engine.getHostList().getHost(action.getDevice())).getLocalQueue().add_method_call(_call_method.DISCOVERY_UPDATE_LOCAL_SERVICE, parameterlist);	
				}
				break;
			case LIST_SERVICES:
				(engine.getHostList().getHost(action.getDevice())).getLocalQueue().add_method_call(_call_method.DISCOVERY_GET_ALL_SERVICES, null);	
				break;
			}
		};
	}
	
	
	
	/**
	 * used by Clock
	 * @param stepNumber
	 * @param actions
	 */
	public void newSimulationInvokationApi(int stepNumber, Vector<HostAction> actions){
		for ( HostAction action : actions ){
			switch (action.getType()){
			case ENABLE_DEVICE_DP_API:
				(engine.getHostList().getHost(action.getDevice())).getDiscoverySocket().enableDiscoveryMechanism();	
				break;
			case DISABLE_DEVICE_DP_API:
				(engine.getHostList().getHost(action.getDevice())).getDiscoverySocket().disableDiscoveryMechanism();				
				break;
			}
		}
		;		
	}

	
	//API for SimulationEngine
	/**
	 * used in TransportLayerScheduler
	 */
	public void sendPhysicalMessage (BluetoothDeviceAddress from, BluetoothDeviceAddress to) throws NetworkException {
		if (!this.getScatternet().isRecvMessgPossible(from.getAddress(), to.getAddress())){
			throw new NetworkException ("Host Not Reachable");
		} 
		try {
			this.getPhysicalNetwork().sendMessage(from, to);			
		} catch (Routing_HostNotReachableException e) {
			throw new NetworkException (e.getMessage());
		} catch (Routing_HostNotAvailableException e) {
			throw new NetworkException (e.getMessage());
//			e.printStackTrace();
		}
	}
	
	/**
	 * used in TransportLayerScheduler
	 * @param message
	 * @param from
	 * @param to
	 * @throws NetworkException
	 */
	public void recvPhysicalMessage(Message message, BluetoothDeviceAddress from, BluetoothDeviceAddress to) throws NetworkException {
		if (!this.getScatternet().isRecvMessgPossible(from.getAddress(), to.getAddress())){
			throw new NetworkException ("Host Not Reachable");
		} 
		try {
			this.getPhysicalNetwork().sendMessage(from, to);			
		} catch (Routing_HostNotReachableException e) {
			throw new NetworkException (e.getMessage());
		} catch (Routing_HostNotAvailableException e) {
			throw new NetworkException (e.getMessage());
//			e.printStackTrace();
		}
	}

	/**
	 * used by ApplicationLayerScheduler
	 * @param msg
	 * @param originHost
	 * @param targetHost
	 */
	public void receiveLogicalMessage(Message msg,
			BluetoothDeviceAddress originHost, BluetoothDeviceAddress targetHost) {
		this.getLogicalNetwork().receiveLogicalMessage(msg, originHost, targetHost);
	}

	/**
	 * used by ApplicationLayerScheduler
	 * @param action
	 */
	public void trackLogicalMessage(NetworkAction action) {
		this.getLogicalNetwork().trackLogicalMessage(action);
	}

	/**
	 * used by ApplicationLayerScheduler
	 * @param action
	 */
	public void invokeLogicalMessageReceivedAckSignal(NetworkAction action) {
		this.getLogicalNetwork().invokeLogicalMessageReceivedAckSignal(action);
	}


	/**
	 * used by ApplicationLayerScheduler
	 * @param action
	 */
	public void sendSignalLogicalMessageLost(NetworkAction action) {
		this.getLogicalNetwork().sendSignalLogicalMessageLost(action);
	}
	
	/**
	 * used by ApplicationLayerScheduler
	 * @param action
	 */
	public void invokeInquiryScan (NetworkAction action){
		this.getLogicalNetwork().invokeInquiryScanResult(action);
	}

	/**
	 * used by ApplicationLayerScheduler
	 * @param host
	 * @param server
	 */
	public void tryToConnectTo(BluetoothDeviceAddress host, BluetoothDeviceAddress server) {
		Host invoker = engine.getHostList().getHost(host);
		Host per = engine.getHostList().getHost(server);
		boolean success = false;
		//--> for connect, per must have an RFCOMM-Socket
		if (per.getRoutingSocket().isRFCOMMServer && !engine.getHostList().isFreshPaired(host, server, engine.getSimulationEnviroment().getClock().getCurrentTime())) {
			// -->try a downside Layer connection
			success = GildedCage.connectingArea(invoker, server);
		}
		HashMap<Integer, BluetoothDeviceAddress> source = new HashMap<Integer, BluetoothDeviceAddress>();
		HashMap<Integer, BluetoothDeviceAddress> target = new HashMap<Integer, BluetoothDeviceAddress>();
		source.put(0, host);
		target.put(0, server);
		if (success){
			NetworkAction nA = new NetworkAction(LogicalNetworkActionType.PAGING_SUCCESS, source, target, PhysicalNetworkActionType.DO_NOTHING, null);
			this.getEngine().getSimulationEnviroment().getQueue().addActionForNextStep(nA);				
		}else {
			NetworkAction nA = new NetworkAction(LogicalNetworkActionType.PAGING_FAILED, source, target, PhysicalNetworkActionType.DO_NOTHING, null);
			this.getEngine().getSimulationEnviroment().getQueue().addActionForNextStep(nA);	
		}
	}

	/**
	 * used by ApplicationLayerScheduler
	 * @param action
	 */
	public void invokePagingFailed (NetworkAction action){
		Host sender = engine.getHostList().getHost(action.getOriginHost());
		
		Vector<Object> parameterlist = new Vector<Object> ();
		parameterlist.add(action.getTargetHost());
		parameterlist.add(new Boolean(false));
		sender.getLocalQueue().add_method_call(_call_method.BLUEZ_INVOKERFCOMMCONNECTREPLY, parameterlist);
		//sender.getRoutingSocket().invokeRfcommConnectReply(action.getTargetHost(), false);
	}
	
	/**
	 * used by ApplicationLayerScheduler
	 * @param action
	 */
	public void invokePagingSuccessed (NetworkAction action){
		Host sender = engine.getHostList().getHost(action.getOriginHost());
		Host target = engine.getHostList().getHost(action.getTargetHost());
		
		//callback --> sender
		Vector<Object> parameterlist_1 = new Vector<Object> ();
		parameterlist_1.add(action.getTargetHost());
		parameterlist_1.add(new Boolean(true));
		sender.getLocalQueue().add_method_call(_call_method.BLUEZ_INVOKERFCOMMCONNECTREPLY, parameterlist_1);
		//sender.getRoutingSocket().invokeRfcommConnectReply(action.getTargetHost(), true);
		
		//callback --> target
		Vector<Object> parameterlist_2 = new Vector<Object> ();
		parameterlist_2.add(action.getOriginHost());
		target.getLocalQueue().add_method_call(_call_method.BLUEZ_ONCLIENTCONNECTED, parameterlist_2);
		//target.getRoutingSocket().onClientConnected(action.getOriginHost());
	}
	
	//API for other Miniworld Elements
	/**
	 * used by GildedCage
	 * @param host
	 * @param maxSlaves
	 * @throws ScatterNetException
	 */
	protected void propagateNewMaster (Host host, int maxSlaves) throws ScatterNetException {
		try {
			if (maxSlaves == -1){
				maxSlaves = 7;
			}
			this.getScatternet().foundNewPiconet(this.engine.getScenarioParameter().getHostDescription(host.getID()), maxSlaves+1);
		} catch (ScatterNet_HostIsAlreadyMasterException e) {
			throw new ScatterNetException (e.getMessage());
		}
	}
	
	/**
	 * used by GildedCage
	 * @param slave
	 * @param master
	 * @throws ScatterNetException
	 */
	protected void propagateNewConnection (Host slave, BluetoothDeviceAddress master) throws ScatterNetException {
		try {
			this.getScatternet().addSlaveToMaster(this.engine.getScenarioParameter().getHostDescription(slave.getID()), Integer.parseInt(master.getFirstPart()));
		} catch (NumberFormatException e) {
			throw new ScatterNetException(e.getMessage());
		} catch (ScatterNet_HostCannotJoinException e) {
			throw new ScatterNetException(e.getMessage());
		}
	}
	
	/**
	 * used by Logical Network
	 * @param device
	 * @return
	 */
	private Vector<Host> getDevicesInRangeForDevice (int device){
		return this.getScatternet().getDevicesInRange(device);
	}

	/**
	 * used by Logical Network
	 * @param device
	 * @return
	 */
	protected Vector<Host> getActiveDevicesInRangeForDevice (int device){
		Vector<Host> tmp = (Vector<Host>) this.getScatternet().getDevicesInRange(device).clone();
		for (int i = 0; i < tmp.size(); i++){
			Host host = tmp.get(i);
			if (!host.isActive()){
				tmp.remove(host);
			}
		}
		return tmp;
	}


	//API for BlueZ Procedures
	/**
	 * used by BlueZ-Api
	 */
	protected void disableDevice(int hostID){
		getRoster().setDeviceInactive(hostID);
		this.getScatternet().setDeviceDisabled(hostID);
	}

	/**
	 * used by BlueZ-Api
	 * @param msg
	 * @param source
	 * @param target
	 */
	protected void sendMessage(Message msg,BluetoothDeviceAddress source , BluetoothDeviceAddress target) {
		this.getLogicalNetwork().sendMessage(source, target, msg);
	}

	/**
	 * used by BlueZ-Api
	 * @param bd_Addr
	 */
	protected void sendInqSignal(BluetoothDeviceAddress bd_Addr) {
		HashMap<Integer, BluetoothDeviceAddress> source = new HashMap<Integer, BluetoothDeviceAddress>();
		source.put(0, bd_Addr);
		NetworkAction nA = new NetworkAction(LogicalNetworkActionType.INQUIRY_SCAN, source, null, PhysicalNetworkActionType.DO_NOTHING, null);
		this.getEngine().getSimulationEnviroment().getQueue().addActionForNextStep(nA);	
	}
	
	/**
	 * used by BlueZ-Api
	 * @param host -> sends the pair inquiry to server
	 * @param server -> this Device should be an Server, a connectionRequest is send to it
	 */
	protected void invokePageDevices(BluetoothDeviceAddress host, BluetoothDeviceAddress server) {
		HashMap<Integer, BluetoothDeviceAddress> source = new HashMap<Integer, BluetoothDeviceAddress>();
		HashMap<Integer, BluetoothDeviceAddress> target = new HashMap<Integer, BluetoothDeviceAddress>();
		source.put(0, host);
		target.put(0, server);
		NetworkAction nA = new NetworkAction(LogicalNetworkActionType.PAGING, source, target, PhysicalNetworkActionType.DO_NOTHING, null);
		this.getEngine().getSimulationEnviroment().getQueue().addActionForNextStep(nA);	
	}
	/**
	 * used by BlueZ-Api
	 * @return the Connections to this point of time, if an connection-building process is invoked by rfcomm_connect, this connection
	 * is possibly not established yet
	 */
	protected Vector<BluetoothDeviceAddress> invokeRfcommShow (BluetoothDeviceAddress invoker){
		return engine.getHostList().getConnectionsTillTimeStamp(invoker, engine.getSimulationEnviroment().getClock().getCurrentTime());
	}
}
